
#define THROW_ERROR_IF_NOT_FAST 1

#include <digitalWriteFast.h>
#include <Adafruit_NeoPixel.h>
#include <EEPROM.h>


#define DEBUG 0
#define SINGLE_PIN 1


#define FLOATING_PIN A0

#if SINGLE_PIN
#define PIN_COUNT 1
#define COMPUTE_MICROS_GREEDY 500
#define COMPUTE_MICROS_OPTIMISTIC 500
#else
#define PIN_COUNT 5
#define COMPUTE_MICROS_GREEDY 250
#define COMPUTE_MICROS_OPTIMISTIC 125
#endif

#if DEBUG
#define CLOCK_MICROS 7000 // time between clock cycles on a given pin
#define CLOCK_TOLERANCE 700 // how early are we allowed to run
#define COMPUTE_MICROS_GREEDY 6000
#define COMPUTE_MICROS_OPTIMISTIC 6000
#define STARTUP_MICROS 2000
#define TIME_TYPE uint32_t
#define TIME_TYPE_MAX ((TIME_TYPE)0xFFFFFFFF)
#define TIME_JUST_OVERFLOWED ((TIME_TYPE)0x40000000)
#define TIME_MIGHT_OVERFLOW ((TIME_TYPE)0xC0000000)
#define CONNECTION_TIMEOUT 5000000
#define FORWARD_CUTOFF 1000000
#define BUTTON_CUTOFF 500000
#else
#define CLOCK_MICROS 550 // time between clock cycles on a given pin
#define CLOCK_TOLERANCE 150 // how early are we allowed to run
#define STARTUP_MICROS 2000
#define TIME_TYPE uint16_t
#define TIME_TYPE_MAX ((TIME_TYPE)0xFFFF)
#define TIME_JUST_OVERFLOWED ((TIME_TYPE)0x4000)
#define TIME_MIGHT_OVERFLOW ((TIME_TYPE)0xC000)
#define CONNECTION_TIMEOUT 600000
#define FORWARD_CUTOFF 1800000
#define BUTTON_CUTOFF 300000
#endif

#if DEBUG && !SINGLE_PIN
#define ERR(reason) { pinModeFast(LED_BUILTIN, OUTPUT); Serial.begin(115200); delay(10); Serial.println(); Serial.println(reason); }
#define DIE(reason) { ERR(reason); while (1) { digitalWriteFast(LED_BUILTIN, LOW);delay(100);digitalWriteFast(LED_BUILTIN, HIGH);delay(500); } }
#define OUT(msg) Serial.println(msg);
#else
#define ERR(reason) { pinModeFast(LED_BUILTIN, OUTPUT); Serial.begin(115200); delay(10); Serial.println(); Serial.println(reason); }
#define DIE(reason) { ERR(reason); while (1) { digitalWriteFast(LED_BUILTIN, LOW);delay(100);digitalWriteFast(LED_BUILTIN, HIGH);delay(500); } }
#define OUT(msg) {};
#endif

#define BRIGHTNESS_BITS 1

// 0b00------: Transmitting
// 0b0000----: tx hdr
// 0b0010----: tx 0
// 0b0011----: tx 1
// 0b0001----: tx complete, waiting for next window
// 0b10------: Receiving
// 0b10000000: prepare to receive
// 0b10000001: ready to receive
// 0b10------: receiving data
// 0b11------: Startup handshake
uint8_t status[PIN_COUNT] = {0};
uint32_t buffer[PIN_COUNT] = {0};
#define PACKET_SIZE 32 // bit count of tx buffer
uint8_t bitCount[PIN_COUNT] = {0};
TIME_TYPE next_wakeup[PIN_COUNT] = {0};

#define COMMS_PIN_SETUP(pin_index, pin) { \
  status[pin_index] = 0xc0; \
  next_wakeup[pin_index] = (pin_index * (CLOCK_MICROS - COMPUTE_MICROS_OPTIMISTIC * (PIN_COUNT / 3))) / PIN_COUNT + STARTUP_MICROS; \
  OUT(pin_index)\
  OUT(next_wakeup[pin_index])\
}

#define TX_NEXT_BIT(pin_index, bit_count) { \
  status[pin_index] = (((buffer[pin_index] >> bit_count) & 1) << 4) | 0b100000; \
}

#define RX_NEXT_BIT(pin_index, bit_count, next_status) { \
  if (bit_count == PACKET_SIZE-1) { \
    /* done on this packet */ \
    status[pin_index] = 0x80; \
  } else { \
    status[pin_index] = next_status; \
  } \
}

#define SWITCH_TO_RX(pin_index, pin) { \
  pinModeFast(pin, INPUT_PULLUP); \
  status[pin_index] = 0x80; \
  bitCount[pin_index] = 0; \
  buffer[pin_index] = 0; \
}

#if DEBUG
#define DELAY_MICROS(t) if (t >= 16383) delayMicroseconds(16383); else delayMicroseconds(t);
#else
#define DELAY_MICROS(t) delayMicroseconds(t);
#endif

#define COMMS_PIN_LOOP(pin_index, pin) {              \
                                                      \
  TIME_TYPE target_time = next_wakeup[pin_index];     \
  {                                                   \
    TIME_TYPE clock_overflow_distance = TIME_TYPE_MAX - target_time; \
    if (clock_overflow_distance < CLOCK_MICROS) {     \
      /* time has overflowed */                       \
      target_time = ((TIME_TYPE)CLOCK_MICROS) - clock_overflow_distance - 1; \
    } else {                                          \
      target_time += CLOCK_MICROS;                    \
    }                                                 \
  };                                                  \
  next_wakeup[pin_index] = target_time;               \
  do {                                                \
    TIME_TYPE current_micros = micros();              \
    TIME_TYPE time_to_wait;                           \
    if (current_micros < TIME_JUST_OVERFLOWED && target_time > TIME_MIGHT_OVERFLOW) { \
      /* time overflowed while we were behind schedule, reset rx state */ \
      ERR(pin_index)\
      ERR(current_micros)\
      ERR(status[pin_index])\
      ERR(target_time)\
      DIE("time overflow behind schedule")                                      \
    } else if (target_time < TIME_JUST_OVERFLOWED && current_micros > TIME_MIGHT_OVERFLOW) { \
      time_to_wait = TIME_TYPE_MAX - current_micros + 1 + target_time; \
    } else if (target_time < current_micros) {        \
      ERR(pin_index)\
      ERR(current_micros)\
      ERR(status[pin_index])\
      ERR(target_time)\
      DIE("running behind schedule")                  \
    } else {                                          \
      time_to_wait = target_time - current_micros;    \
    }                                                 \
    if (time_to_wait >= COMPUTE_MICROS_GREEDY) {      \
      compute_loop();                                 \
    } else if (time_to_wait > CLOCK_TOLERANCE) {      \
      DELAY_MICROS(time_to_wait - CLOCK_TOLERANCE)    \
      break;                                          \
    } else {                                          \
      break;                                          \
    }                                                 \
  } while (1);                                        \
                                                      \
  uint8_t pinStatus = status[pin_index]++;            \
                                                      \
  switch (pinStatus) {                                \
  case 0x0c:                                          \
    /* almost done sending hdr, move to bit state */  \
    TX_NEXT_BIT(pin_index, 0)                         \
    break;                                            \
  case 0x20:                                          \
  case 0x30:                                          \
    digitalWriteFast(pin, HIGH);                      \
    break;                                            \
  case 0x00:                                          \
    ERR("invalid null state");                        \
    break;                                            \
  case 0x01: /* start sending header */               \
  case 0x23: /* start sending "0" */                  \
  case 0x33: /* start sending "1" */                  \
    digitalWriteFast(pin, LOW);                       \
    break;                                            \
  case 0x25:                                          \
    /* almost at end of "0" */                        \
    /* fallthrough */                                 \
  case 0x38: {                                        \
    /* almost at end of "1" */                        \
    /* careful of off-by-one (or two) errors */       \
    if (bitCount[pin_index] < PACKET_SIZE-1) {        \
      TX_NEXT_BIT(pin_index, ++bitCount[pin_index])   \
    }                                                 \
    break; }                                          \
  case 0x26:                                          \
  case 0x39:                                          \
    /* done on this packet */                         \
    TX_DONE_CB(pin_index, pin);                       \
    break;                                            \
  case 0x0f:                                          \
    pinModeFast(pin, OUTPUT);                         \
    status[pin_index] = 0x11;                         \
    /* fallthrough */                                 \
  case 0x10:                                          \
    /* pre-header delay */                            \
    digitalWriteFast(pin, HIGH);                      \
    break;                                            \
  case 0x1b:                                          \
    /* almost done waiting, check for data */         \
    if (bitCount[pin_index] == 0) {                   \
      /* there is data, send header */                \
      status[pin_index] = 0x01;                       \
    } else {                                          \
      /* check again for data in one cycle time */    \
      status[pin_index] = 0x1b;                       \
    }                                                 \
    break;                                            \
  case 0xc0:                                          \
    if (random(2)) {             \
      /* try rx first */                              \
      pinModeFast(pin, INPUT_PULLUP);                 \
    } else {                                          \
      /* try tx first */                              \
      pinModeFast(pin, OUTPUT);                       \
      digitalWriteFast(pin, LOW);                     \
      status[pin_index] = 0xd1;                       \
    }                                                 \
    break;                                            \
  case 0xc1:                                          \
    if (digitalReadFast(pin) == LOW) {                \
      /* if we are on the tail end of a pulse we can't tell if it is an ack or a ping */ \
      status[pin_index] = 0xc1;                       \
    }                                                 \
    break;                                            \
  case 0xca:                                          \
    status[pin_index] = 0xc0;                         \
    /* fallthrough */                                 \
  case 0xc2:                                          \
  case 0xc3:                                          \
  case 0xc4:                                          \
  case 0xc5:                                          \
  case 0xc6:                                          \
  case 0xc7:                                          \
  case 0xc8:                                          \
  case 0xc9:                                          \
    if (digitalReadFast(pin) == LOW) {                \
      /* if there is someone there, we go low to ack */ \
      status[pin_index] = 0xe1;                       \
      pinModeFast(pin, OUTPUT);                       \
      digitalWriteFast(pin, LOW);                     \
    }                                                 \
    break;                                            \
  case 0xd3:                                          \
    pinModeFast(pin, INPUT_PULLUP);                   \
    /* fallthrough */                                 \
  case 0xd4:                                          \
  case 0xd5:                                          \
  case 0xd6:                                          \
  case 0xd7:                                          \
    if (digitalReadFast(pin) == HIGH) {               \
      /* other side did not ack correctly */          \
      status[pin_index] = 0xc0;                       \
    }                                                 \
    break;                                            \
  case 0xd8:                                          \
    /* negotiated rx */                               \
    SWITCH_TO_RX(pin_index, pin)                      \
    conn_init_cb(pin_index);                          \
    break;                                            \
  case 0xe9:                                          \
    /* negotiated tx */                               \
    status[pin_index] = 0x0f;                         \
    bitCount[pin_index] = PACKET_SIZE;                \
    conn_init_cb(pin_index);                          \
    break;                                            \
  case 0x80:                                          \
  case 0x81:                                          \
  case 0x82:                                          \
  case 0x83:                                          \
  case 0x84:                                          \
  case 0x85:                                          \
  case 0x86:                                          \
  case 0x87:                                          \
  case 0x88:                                          \
  case 0x89:                                          \
  case 0x8a:                                          \
    if (digitalReadFast(pin) == HIGH || bitCount[pin_index]) { \
      /* partial/no hdr or not ready to rx, reset */  \
      status[pin_index] = 0x80;                       \
    }                                                 \
    break;                                            \
  case 0x8d:                                          \
    /* expect high */                                 \
    if (digitalReadFast(pin) == LOW) {                \
      /* too much hdr, reset */                       \
      status[pin_index] = 0x80;                       \
      bitCount[pin_index] = 0;                        \
      buffer[pin_index] = 0;                          \
    }                                                 \
    break;                                            \
  case 0x90:                                          \
    /* read pulse first half */                       \
    if (digitalReadFast(pin) == HIGH) {               \
      /* missing pulse, reset */                      \
      status[pin_index] = 0x80;                       \
      bitCount[pin_index] = 0;                        \
      buffer[pin_index] = 0;                          \
    }                                                 \
    break;                                            \
  case 0x93: {                                        \
    /* read pulse second half */                      \
    uint8_t bit_offset = bitCount[pin_index]++;       \
    if (digitalReadFast(pin) == LOW) {                \
      buffer[pin_index] |= ((uint32_t)1) << bit_offset; \
      RX_NEXT_BIT(pin_index, bit_offset, 0x8b)        \
    } else {                                          \
      /* short pulse, expect the next one */          \
      RX_NEXT_BIT(pin_index, bit_offset, 0x8e)        \
    }                                                 \
    break; }                                          \
  }                                                   \
}


// packet format: update neighbours (00):
// 0xX------- link sender
// 0x-X------ original sender
// 0x--43210- neighbours (0-4)
// 0x-------X flags and parity bit
// flags:
// 0b00-X odd parity
// 0b00X- swap roles

// packet format: button pressed (01):
// 0xX------- link sender
// 0x-X------ original sender
// 0x--XX---- button states
// 0x-------X flags and parity bit
// flags:
// 0b01-X odd parity
// 0b01X- swap roles

// packet format: set LED colour (1X):
// 0xX------- link sender
// 0x-X------ original sender
// 0x--X----- recipient
// 0x---X---- LED index
// 0x----RGB- new LED colour (12-bit)
// 0x-------X flags and parity bit
// flags:
// 0b1--X odd parity
// 0b1-X- swap roles
// 0b1X-- reset all LEDs
// The physical LEDs have indices from 0 to 5. Setting index 6 will control the neighbour reporting feature.
// Recipient 0 will ignore the recipient.

uint8_t current_pin_index = 0;
uint32_t last_packets_by_remote[16] = {0};
uint32_t last_packet_times_by_remote[16] = {0};
uint32_t mask_link_remotes = 0;
uint32_t last_rx_times_by_pin[PIN_COUNT] = {0};
uint8_t last_remote_packet_forwarded[PIN_COUNT] = {0};
// circular buffer of most recent 5 pins where we received a message from each remote
// rppbr[r][0] is the pointer index
// pins are stored as pin_index+1 (0 is null)
uint8_t recent_packet_pins_by_remote[16][6] = {0};
uint32_t buttonPressTime = 0;

// populated in setup
uint32_t mask_link_sender;
uint32_t mask_myself_remote_sender;
uint8_t my_device_id;
uint32_t mask_recipient_me;


uint8_t buttonState = 0;
Adafruit_NeoPixel led_strips[2];
uint32_t pixels[6] = {0};
uint8_t led_strips_need_update = {0};
#if SINGLE_PIN
uint8_t serial_state = 0;
#endif
bool enable_neighbours = 1;



// http://www.graphics.stanford.edu/%7Eseander/bithacks.html#ParityParallel
inline bool compute_parity(uint32_t v) {
  v ^= v >> 16;
  v ^= v >> 8;
  v ^= v >> 4;
  v &= 0xf;
  return (0x6996 >> v) & 1;
}

inline void send_state(bool swap_roles) {
  uint32_t m;
  if (mask_link_remotes && enable_neighbours) {
    m = mask_myself_remote_sender | mask_link_remotes;
  } else {
    // no point sending a claim that we are not connected to any node as this is the default state
    // instead send an invalid empty message and yield (so we can discover who we are connected to)
    // this encourages early yielding at the start of a connection and so improved initialisation times
    // Also bail if sending neighbours is disabled
    m = 0;
    swap_roles = true;
  }
  send_msg(m, swap_roles);
}

inline void send_msg(uint32_t m, bool swap_roles) {
  m &= ~0b10; 
  m |= swap_roles << 1;
  m &= ~0xF0000000;
  m |= mask_link_sender;
  
  buffer[current_pin_index] = m ^ compute_parity(m) ^ 0b1;
  OUT("send")
  OUT(buffer[current_pin_index])
  bitCount[current_pin_index] = 0;
}

void dump_compute_state() {
  ERR(current_pin_index)
  ERR(bitCount[current_pin_index])
  ERR(buffer[current_pin_index])
  ERR(status[current_pin_index])
}

#define END_COMPUTE goto out;

void compute_loop() {
  uint32_t currentMicros = micros();
  if (status[0] > 0xC0) {
    digitalWriteFast(LED_BUILTIN, HIGH);
  } else {
    digitalWriteFast(LED_BUILTIN, LOW);
  }
  if (status[current_pin_index] > 0xC0) {
    mask_link_remotes &= ~((uint32_t)0xF0 << (current_pin_index * 4));
  }

  if (current_pin_index == 0) {
    OUT(((uint16_t)bitCount[current_pin_index] << 8) | status[current_pin_index])
    //OUT(status[current_pin_index])
  }

#if SINGLE_PIN
  // keep the serial buffer in a good state
  uint8_t available = Serial.available();
  if (available == 63) {
    // buffer full, clean slate
    for (; Serial.read() >= 0;);
    available = 0;
    serial_state = 0;
    Serial.write(0);
    Serial.write(0);
    Serial.write(0);
    Serial.write(0);
  }
  while ((serial_state < 8 && available > 0) || available > 4) {
    uint8_t c = Serial.read();
    available--;
    if (serial_state < 4) {
      if (c == 0xFF) {
        serial_state++;
      } else {
        serial_state = 0;
      }
    } else /* if (serial_state < 8) */ {
      if (c == 0x00) {
        serial_state++;
      } else if (c == 0xFF) {
        serial_state = 4;
      } else {
        serial_state = 0;
      }
    }
  }
#endif


#if !SINGLE_PIN
  uint8_t new_button_state = ~(digitalReadFast(0) | digitalReadFast(2) << 1 | digitalReadFast(3) << 2 | digitalReadFast(4) << 3 | digitalReadFast(5) << 4 | digitalReadFast(6) << 5 | 0b11000000);
  if (new_button_state && new_button_state ^ buttonState) {
    buttonState = new_button_state;
    buttonPressTime = currentMicros + BUTTON_CUTOFF;
  }
  if (buttonPressTime < currentMicros) {
    buttonState = 0;
  }
#endif

  // global operation:
  // 1. send my id to each neighbour
  // 2. send id of all discovered neighbours to all other neighbours
  // 3. go to rx and prune disconnected neighbours

  if (status[current_pin_index] >> 6 == 0b10 && bitCount[current_pin_index] == PACKET_SIZE) {
    // just finished receiving
    uint32_t msg = buffer[current_pin_index];
    buffer[current_pin_index] = 0;
    bitCount[current_pin_index] = 0;
    OUT(msg)
    if (!compute_parity(msg)) {
      // parity incorrect, discard
      goto out;
    }
    last_rx_times_by_pin[current_pin_index] = currentMicros;
    if (msg & 0b10) {
      // switch to tx
      status[current_pin_index] = 0x0f;
      bitCount[current_pin_index] = PACKET_SIZE;
    }
#if !SINGLE_PIN
    mask_link_remotes = (mask_link_remotes & ~((uint32_t)0xF0 << (current_pin_index * 4))) | ((msg & 0xF0000000) >> (24 - (current_pin_index * 4)));
    uint8_t origin = (msg >> 24) & 0xF;
    bool forward = true;
    if (msg & 0b1000) {
      bool handle = true;
      if ((msg & 0x00F00000) == mask_recipient_me) {
        forward = false;
      } else if ((msg & 0x00F00000) != 0) {
        handle = false;
      }

      if (handle) {
        // update LEDs
        uint8_t led_index = (msg & 0x000F0000) >> 16;

        if (led_index == 6) {
          enable_neighbours = (msg & 0x0000FFF0) > 0;
        } else if (led_index < 6) {
          uint8_t strip_index = led_index & 1;
          OUT("led")
          if (msg & 0b0100) {
            pixels[strip_index] = pixels[2 + strip_index] = pixels[4 + strip_index] = 0;
          }
          pixels[led_index] = (msg & 0x0000F000) << (8 - BRIGHTNESS_BITS) | (msg & 0x00000F00) << (3 - BRIGHTNESS_BITS) | (msg & 0x000000F0) >> BRIGHTNESS_BITS;
          led_strips[strip_index].setPixelColor(0, pixels[strip_index]);
          led_strips[strip_index].setPixelColor(1, pixels[2 + strip_index]);
          led_strips[strip_index].setPixelColor(2, pixels[4 + strip_index]);
          if (led_strips[strip_index].canShow()) {
            led_strips[strip_index].show();
          } else {
            led_strips_need_update |= 1 << strip_index;
          }
          if (msg & 0b0100) {
            strip_index = 1 - strip_index;
            pixels[strip_index] = pixels[2 + strip_index] = pixels[4 + strip_index] = 0;
            led_strips_need_update |= 1 << strip_index;
          }
        }
      }
    }
    if (forward) {
      // forward packet, it wasn't for us
      uint8_t *recent_packet_pins = recent_packet_pins_by_remote[origin];
      recent_packet_pins[0] = (recent_packet_pins[0] % 5) + 1;
      recent_packet_pins[recent_packet_pins[0]] = current_pin_index + 1;
      last_packets_by_remote[origin] = msg;
      last_packet_times_by_remote[origin] = currentMicros;
    }
#else
    if (Serial.availableForWrite() >= 4) {
      OUT("recv")
      OUT(buffer[0])
      Serial.write((char *)&msg, 4);
    }
#endif
    goto out;
  }
  if (status[current_pin_index] >> 6 == 0b00 && bitCount[current_pin_index] == PACKET_SIZE) {
    // send next
#if !SINGLE_PIN
    // check remote is still here by yielding control
    bool yield = last_rx_times_by_pin[current_pin_index] + (CONNECTION_TIMEOUT / 2) < currentMicros;
    uint32_t forward_cutoff_time;
    if (currentMicros < FORWARD_CUTOFF) {
      forward_cutoff_time = 0;
    } else {
      forward_cutoff_time = currentMicros - FORWARD_CUTOFF;
    }
    if (buttonPressTime > currentMicros) {
      send_msg(mask_link_sender | mask_myself_remote_sender | ((uint32_t)buttonState) << 16 | 0b0100, yield);
      goto out;
    }
    uint8_t next_remote_for_packet_forwarding = last_remote_packet_forwarded[current_pin_index];
    do {
      next_remote_for_packet_forwarding = (next_remote_for_packet_forwarding + 1) % 16;
      // missing or outdated packet or come full circle
    } while (
      (
        // remote 0 doesn't exist
        next_remote_for_packet_forwarding == 0
        // packet too old to forward
        || last_packet_times_by_remote[next_remote_for_packet_forwarding] < forward_cutoff_time
        // no packet stored from this remote
        || last_packets_by_remote[next_remote_for_packet_forwarding] == 0
        // recently received a message from this remote on this pin (1-indexed)
        || recent_packet_pins_by_remote[next_remote_for_packet_forwarding][1] == current_pin_index + 1
        || recent_packet_pins_by_remote[next_remote_for_packet_forwarding][2] == current_pin_index + 1
        || recent_packet_pins_by_remote[next_remote_for_packet_forwarding][3] == current_pin_index + 1
        || recent_packet_pins_by_remote[next_remote_for_packet_forwarding][4] == current_pin_index + 1
        || recent_packet_pins_by_remote[next_remote_for_packet_forwarding][5] == current_pin_index + 1
      )
      && !(
        // time to send our state
        next_remote_for_packet_forwarding == my_device_id
        // tried all remotes, nothing is eligible
        || next_remote_for_packet_forwarding == last_remote_packet_forwarded[current_pin_index]
      )
    );
    if (next_remote_for_packet_forwarding == last_remote_packet_forwarded[current_pin_index]) {
      // nothing we can forward, start listening instead
      yield = true;
    }
    if (
      next_remote_for_packet_forwarding == my_device_id
      || next_remote_for_packet_forwarding == last_remote_packet_forwarded[current_pin_index]
    ) {
      OUT("nothing to forward")
      send_state(yield);
      last_remote_packet_forwarded[current_pin_index] = my_device_id;
      goto out;
    }
    last_remote_packet_forwarded[current_pin_index] = next_remote_for_packet_forwarding;
    send_msg(last_packets_by_remote[next_remote_for_packet_forwarding], yield);
#else
    if (serial_state == 8 && Serial.available() >= 4) {
      OUT("send")
      Serial.readBytes((char *)&buffer[0], 4);
      if (!compute_parity(buffer[0])) {
        OUT("invalid message to send")
      } else {
        bitCount[current_pin_index] = 0;
      }
      OUT(buffer[0])
      if (buffer[0] == 0xFFFFFFFF) {
        serial_state = 4;
      } else if ((buffer[0] & 0xFFFFFF) == 0xFFFFFF) {
        serial_state = 2;
      } else if ((buffer[0] & 0xFFFF) == 0xFFFF) {
        serial_state = 1;
      } else if ((buffer[0] & 0xFF) == 0xFF) {
        serial_state = 1;
      } else {
        serial_state = 0;
      }
      if (!(buffer[0] & 0b10)) {
        // signal ready for next packet
        Serial.write(0);
        Serial.write(0);
        Serial.write(0);
        Serial.write(0);
      }
    }
#endif
    goto out;
  }
#if !SINGLE_PIN
  if (led_strips_need_update & 1 && led_strips[0].canShow()) {
    led_strips_need_update &= ~1;
    led_strips[0].setPixelColor(0, pixels[0]);
    led_strips[0].setPixelColor(1, pixels[2]);
    led_strips[0].setPixelColor(2, pixels[4]);
    led_strips[0].show();
    goto out;
  }
  if (led_strips_need_update & 2 && led_strips[1].canShow()) {
    led_strips_need_update &= ~2;
    led_strips[1].setPixelColor(0, pixels[1]);
    led_strips[1].setPixelColor(1, pixels[3]);
    led_strips[1].setPixelColor(2, pixels[5]);
    led_strips[1].show();
    goto out;
  }
#endif
  if (status[current_pin_index] >> 6 == 0b10 && last_rx_times_by_pin[current_pin_index] + CONNECTION_TIMEOUT < currentMicros) {
    // they had their chance
    OUT("timeout")
    mask_link_remotes &= ~((uint32_t)0xF0 << (current_pin_index * 4));
    status[current_pin_index] = 0xc0;
    goto out;
  }
  // if tx timeout occurs we will not notice but eventually we will yield and it will sort itself out
  // this is most likely to cause problems on a single-pin node
  // if it happens there, it means the host controller was too slow

  out:
#if !SINGLE_PIN
  current_pin_index = (current_pin_index + 1) % PIN_COUNT;
#else
  ;
#endif
}

#define TX_DONE_CB(pin_index, pin) { \
  if (buffer[pin_index] & 0x00000002) { \
    /* swap roles was set */ \
    SWITCH_TO_RX(pin_index, pin) \
    last_rx_times_by_pin[pin_index] = micros(); \
  } else { \
    digitalWriteFast(pin, HIGH); \
    status[pin_index] = 0x11; \
    bitCount[pin_index] = PACKET_SIZE; \
  } \
  OUT("tx done cb") \
}

inline void conn_init_cb(uint8_t pin_index) {
  last_rx_times_by_pin[pin_index] = micros();
#if SINGLE_PIN
  if (!(status[pin_index] & 0x80)) {
    // signal ready for next packet
    Serial.write(0);
    Serial.write(0);
    Serial.write(0);
    Serial.write(0);
  }
#endif
}

void setup() {
  randomSeed(analogRead(A0) | analogRead(A1) << 10UL | (unsigned long)analogRead(A2) << 20UL | (unsigned long)analogRead(A3) << 30);
  pinModeFast(LED_BUILTIN, OUTPUT);
  digitalWriteFast(LED_BUILTIN, LOW);

  #if DEBUG || SINGLE_PIN
  Serial.begin(115200);
  while (!Serial);
  #endif

  #if SINGLE_PIN
  while (Serial.available()) {
    Serial.read();
  }
  #endif

  my_device_id = EEPROM.read(0);
  mask_link_sender = ((uint32_t)my_device_id << 28);
  mask_myself_remote_sender = ((uint32_t)my_device_id << 24);
  mask_recipient_me = ((uint32_t)my_device_id << 20);

  led_strips[0] = Adafruit_NeoPixel(3, 12, NEO_GRB + NEO_KHZ800);
  led_strips[1] = Adafruit_NeoPixel(3, 13, NEO_GRB + NEO_KHZ800);
  for (int i = 0; i < 2; i++) {
    led_strips[i].begin();
  }

#if !SINGLE_PIN
  pinModeFast(0, INPUT_PULLUP);
  pinModeFast(2, INPUT_PULLUP);
  pinModeFast(3, INPUT_PULLUP);
  pinModeFast(4, INPUT_PULLUP);
  pinModeFast(5, INPUT_PULLUP);
  pinModeFast(6, INPUT_PULLUP);
#endif

  COMMS_PIN_SETUP(0,7)
#if !SINGLE_PIN
  COMMS_PIN_SETUP(1,8)
  COMMS_PIN_SETUP(2,9)
  COMMS_PIN_SETUP(3,10)
  COMMS_PIN_SETUP(4,11)
#endif

}

void loop() {
  while (1) {
    COMMS_PIN_LOOP(0,7)
#if !SINGLE_PIN
    COMMS_PIN_LOOP(1,8)
    COMMS_PIN_LOOP(2,9)
    COMMS_PIN_LOOP(3,10)
    COMMS_PIN_LOOP(4,11)
#endif
  }
}
